package com.googlecode.horncomb.orm.ibatis;

import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import static org.apache.commons.lang.StringUtils.*;
import static org.apache.commons.lang.Validate.*;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport;

import com.googlecode.horncomb.orm.support.filter.Condition;
import com.googlecode.horncomb.orm.support.filter.MapConditionImpl;
import com.googlecode.horncomb.orm.support.order.Order;
import com.googlecode.horncomb.orm.support.paged.PagedList;
import com.googlecode.horncomb.util.lang.GenericsUtils;

@SuppressWarnings("serial")
public abstract class AbstractSqlMapEntityDaoImpl<EntityType extends Serializable>
		extends SqlMapClientDaoSupport implements SqlMapEntityDao<EntityType> {

	/** entity class type */
	@SuppressWarnings("unchecked")
	protected Class entityClass;

	/** use namespace as prefix of a stmt, or not */
	protected boolean useStatementNamespaces = true;
	/** the namespace */
	protected String namespace = null;

	/** the name of get statement */
	protected String getStatementName = "get"; //$NON-NLS-1$
	/** the name of insert statement */
	protected String insertStatementName = "insert"; //$NON-NLS-1$
	/** the name of update statement */
	protected String updateStatementName = "update"; //$NON-NLS-1$
	/** the name of delete statement */
	protected String deleteStatementName = "delete"; //$NON-NLS-1$
	/** the name of query statement */
	protected String queryStatementName = "query"; //$NON-NLS-1$
	/** the name of query count statement */
	protected String queryCountStatementName = "queryCount"; //$NON-NLS-1$

	/**
	 *
	 */
	public AbstractSqlMapEntityDaoImpl() {
		super();
	}

	@Override
	protected void initDao() throws Exception {
		super.initDao();
		if (this.entityClass == null) {
			this.entityClass = GenericsUtils.getFirstGenericClass(this
					.getClass());
		}
		notNull(this.entityClass);

		if (this.useStatementNamespaces) {
			if (isEmpty(this.namespace)) {
				this.namespace = this.entityClass.getName();
			}
		}
		notEmpty(this.namespace);
	}

	@SuppressWarnings("unchecked")
	public EntityType createNewEntity() throws DataAccessException {
		try {
			return (EntityType) this.entityClass.newInstance();
		} catch (InstantiationException e) {
			throw new DataRetrievalFailureException(
					"cannot create new entity instance.", e);
		} catch (IllegalAccessException e) {
			throw new DataRetrievalFailureException(
					"cannot create new entity instance.", e);
		}
	}

	/**
	 * {@inheritDoc}
	 * @see cn.horncomb.orm.dao.EntityBaseDao#deleteById(java.io.Serializable)
	 */
	public void deleteById(Serializable id) throws DataAccessException {
		this.deleteById(id, this.deleteStatementName);
	}

	public void deleteByIds(Set<? extends Serializable> ids)
			throws DataAccessException {
		this.deleteByIds(ids, this.deleteStatementName);
	}

	/**
	 * @param id
	 * @param stmtName
	 * @throws DataAccessException
	 */
	protected abstract void deleteById(Serializable id, String stmtName)
			throws DataAccessException;

	/**
	 * @param ids
	 * @param stmtName
	 * @throws DataAccessException
	 */
	protected abstract void deleteByIds(Set<? extends Serializable> ids,
			String stmtName) throws DataAccessException;

	/**
	 * @param constraints
	 * @throws DataAccessException
	 */
	protected void deleteByQuery(Condition constraints)
			throws DataAccessException {
		this.deleteByQuery(constraints, this.deleteStatementName);
	}

	/**
	 * delete by constraints
	 * @param constraints
	 * @param stmtName
	 *            the name of statement
	 * @throws DataAccessException
	 */
	protected abstract void deleteByQuery(Condition constraints, String stmtName)
			throws DataAccessException;

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.dao.EntityBaseDao#getById(java.io.Serializable)
	 */
	public EntityType getById(Serializable id) throws DataAccessException {
		return this.getById(id, this.getStatementName);
	}

	/**
	 * @param id
	 * @param stmtName
	 *            the name of statement
	 * @return
	 * @throws DataAccessException
	 */
	protected abstract EntityType getById(Serializable id, String stmtName)
			throws DataAccessException;

	/**
	 * @param cons
	 * @param stmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected EntityType getByQuery(Condition cons) throws DataAccessException {
		return this.getByQuery(cons, this.getStatementName);
	}

	/**
	 * @param cons
	 * @param stmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected abstract EntityType getByQuery(Condition cons, String stmtName)
			throws DataAccessException;

	/**
	 * {@inheritDoc}
	 * @see cn.horncomb.orm.dao.EntityBaseDao#insert(java.io.Serializable)
	 */
	public void insert(EntityType entity) throws DataAccessException {
		this.insert(entity, this.insertStatementName);
	}

	/**
	 * insert a entity to database
	 * @param entity
	 *            the entity for inserting
	 * @param stmtName
	 *            the name of statement
	 * @throws DataAccessException
	 */
	protected abstract void insert(EntityType entity, String stmtName)
			throws DataAccessException;

	/**
	 * {@inheritDoc}
	 * @see cn.horncomb.orm.dao.EntityBaseDao#update(java.io.Serializable)
	 */
	public void update(EntityType entity) throws DataAccessException {
		this.update(entity, this.updateStatementName);
	}

	/**
	 * @param entity
	 * @param stmtName
	 * @throws DataAccessException
	 */
	protected abstract void update(EntityType entity, String stmtName)
			throws DataAccessException;

	/**
	 * {@inheritDoc}
	 * @see cn.horncomb.orm.dao.EntityBaseDao#save(java.io.Serializable)
	 */
	public void save(EntityType entity) throws DataAccessException {
		if (this.isNew(entity)) {
			this.insert(entity);
		} else {
			this.update(entity);
		}
	}

	/**
	 * if the entity is new to save
	 * @param entity
	 * @return
	 */
	protected abstract boolean isNew(EntityType entity);

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.SqlMapEntityDao#queryByExample(java.io.Serializable,
	 *      java.util.List, int, int)
	 */
	public PagedList<? extends Collection<EntityType>> queryByExample(
			EntityType example, Order order, int page, int pageSize) {
		return this.query(this.convertExampleToConstraints(example), order,
				page, pageSize);
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.SqlMapEntityDao#queryCountByExample(java.io.Serializable)
	 */
	public int queryCountByExample(EntityType example) {
		return this.queryCount(this.convertExampleToConstraints(example));
	}

	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.SqlMapEntityDao#queryByExample(java.io.Serializable,
	 *      java.util.List)
	 */
	public Collection<EntityType> queryByExample(EntityType example, Order order)
			throws DataAccessException {
		return this.query(this.convertExampleToConstraints(example), order);
	}

	/**
	 * @param constraints
	 * @param order
	 * @return
	 * @throws DataAccessException
	 */
	protected Collection<EntityType> query(Condition constraints, Order order)
			throws DataAccessException {
		return this.query(constraints, order, this.queryStatementName);
	}

	/**
	 * @param constraints
	 * @param order
	 * @param page
	 * @param pageSize
	 * @return
	 * @throws DataAccessException
	 */
	protected PagedList<? extends Collection<EntityType>> query(
			Condition constraints, Order order, int page, int pageSize)
			throws DataAccessException {
		return this.query(constraints, order, page, pageSize,
				this.queryStatementName, this.queryCountStatementName);
	}

	/**
	 * 获得记录数
	 * @param constraints
	 * @return
	 */
	protected int queryCount(Condition constraints) {
		return this.queryCount(constraints, this.queryCountStatementName);
	}

	// ----------------------------------------------------------------------
	// query function
	/**
	 * @param constraints
	 * @param order
	 * @param qryStmtName
	 * @return
	 * @throws DataAccessException
	 */
	protected abstract Collection<EntityType> query(Condition constraints,
			Order order, String qryStmtName) throws DataAccessException;

	/**
	 * @param constraints
	 * @param order
	 * @param page
	 * @param pageSize
	 * @param qryStmtName
	 * @param queryCountStmtId
	 * @return
	 * @throws DataAccessException
	 */
	protected abstract PagedList<? extends Collection<EntityType>> query(
			Condition constraints, Order order, int page, int pageSize,
			String qryStmtName, String queryCountStmtId)
			throws DataAccessException;

	/**
	 * @param constraints
	 * @param qryCntStmtName
	 * @return
	 */
	protected abstract int queryCount(Condition constraints,
			String qryCntStmtName);

	// ----------------------------------------------------------------------
	/**
	 * @param example
	 * @return
	 */
	private Condition convertExampleToConstraints(EntityType example) {
		Condition qry = new MapConditionImpl();
		qry.getConstraintsMap().putAll(this.convertObjectToMap(example));
		return qry;
	}

	/**
	 * @param entity
	 * @return
	 */
	private Map<String, Object> convertObjectToMap(EntityType entity) {
		Map<String, Object> map = new HashMap<String, Object>();
		PropertyDescriptor[] pd = PropertyUtils.getPropertyDescriptors(entity);
		for (int i = 0; i < pd.length; i++) {
			try {
				map.put(pd[i].getDisplayName(), PropertyUtils.getReadMethod(
						pd[i]).invoke(entity));
			} catch (Exception e) {
				this.logger.debug("通过属性的只读方法获取数失败", e); //$NON-NLS-1$
			}
		}
		return map;
	}

	// ----------------------------------------------------------------------
	/**
	 * @param getStatementName
	 *            the getStatementName to set
	 */
	public void setGetStatementName(String name) {
		notEmpty(name);
		this.getStatementName = name;
	}

	/**
	 * @param insertStatementName
	 *            the insertStatementName to set
	 */
	public void setInsertStatementName(String name) {
		notEmpty(name);
		this.insertStatementName = name;
	}

	/**
	 * @param updateStatementName
	 *            the updateStatementName to set
	 */
	public void setUpdateStatementName(String name) {
		notEmpty(name);
		this.updateStatementName = name;
	}

	/**
	 * @param deleteStatementName
	 *            the deleteStatementName to set
	 */
	public void setDeleteStatementName(String name) {
		notEmpty(name);
		this.deleteStatementName = name;
	}

	/**
	 * @param queryStatementName
	 *            the queryStatementName to set
	 */
	public void setQueryStatementName(String name) {
		notEmpty(name);
		this.queryStatementName = name;
	}

	/**
	 * @param queryCountStatementName
	 *            the queryCountStatementName to set
	 */
	public void setQueryCountStatementName(String name) {
		notEmpty(name);
		this.queryCountStatementName = name;
	}

	// ----------------------------------------------------------------------
	/**
	 * {@inheritDoc}
	 * @see com.googlecode.horncomb.orm.ibatis.SqlMapEntityDao#setEntityClass(java.lang.Class)
	 */
	public void setEntityClass(Class<?> entityClass) {
		notNull(entityClass);
		this.entityClass = entityClass;
	}

	public void setNamespace(String namespace) {
		notEmpty(namespace);
		this.namespace = namespace;
	}

	/**
	 * @param useStatementNamespaces
	 *            the useStatementNamespaces to set
	 */
	public void setUseStatementNamespaces(boolean useStatementNamespaces) {
		this.useStatementNamespaces = useStatementNamespaces;
	}
}
